storage/webstorage/
mod.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
5mod engines;
6
7use std::borrow::ToOwned;
8use std::collections::BTreeMap;
9use std::path::PathBuf;
10use std::sync::Arc;
11use std::thread;
12
13use base::generic_channel::{self, GenericReceiver, GenericSender};
14use base::id::WebViewId;
15use base::threadpool::ThreadPool;
16use malloc_size_of::MallocSizeOf;
17use malloc_size_of_derive::MallocSizeOf;
18use profile_traits::mem::{
19    ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, perform_memory_report,
20};
21use profile_traits::path;
22use rustc_hash::FxHashMap;
23use servo_config::pref;
24use servo_url::{ImmutableOrigin, ServoUrl};
25use storage_traits::webstorage_thread::{StorageType, WebStorageThreadMsg};
26use uuid::Uuid;
27
28use crate::webstorage::engines::WebStorageEngine;
29use crate::webstorage::engines::sqlite::SqliteEngine;
30
31const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024;
32
33pub trait WebStorageThreadFactory {
34    fn new(config_dir: Option<PathBuf>, mem_profiler_chan: MemProfilerChan) -> Self;
35}
36
37impl WebStorageThreadFactory for GenericSender<WebStorageThreadMsg> {
38    /// Create a storage thread
39    fn new(
40        config_dir: Option<PathBuf>,
41        mem_profiler_chan: MemProfilerChan,
42    ) -> GenericSender<WebStorageThreadMsg> {
43        let (chan, port) = generic_channel::channel().unwrap();
44        let chan2 = chan.clone();
45        thread::Builder::new()
46            .name("WebStorageManager".to_owned())
47            .spawn(move || {
48                mem_profiler_chan.run_with_memory_reporting(
49                    || WebStorageManager::new(port, config_dir).start(),
50                    String::from("storage-reporter"),
51                    chan2,
52                    WebStorageThreadMsg::CollectMemoryReport,
53                );
54            })
55            .expect("Thread spawning failed");
56        chan
57    }
58}
59
60#[derive(Clone, Default, MallocSizeOf)]
61pub struct OriginEntry {
62    tree: BTreeMap<String, String>,
63    size: usize,
64}
65
66impl OriginEntry {
67    pub fn inner(&self) -> &BTreeMap<String, String> {
68        &self.tree
69    }
70
71    pub fn insert(&mut self, key: String, value: String) -> Option<String> {
72        let old_value = self.tree.insert(key.clone(), value.clone());
73        let size_change = match &old_value {
74            Some(old) => value.len() as isize - old.len() as isize,
75            None => (key.len() + value.len()) as isize,
76        };
77        self.size = (self.size as isize + size_change) as usize;
78        old_value
79    }
80
81    pub fn remove(&mut self, key: &str) -> Option<String> {
82        let old_value = self.tree.remove(key);
83        if let Some(old) = &old_value {
84            self.size -= key.len() + old.len();
85        }
86        old_value
87    }
88
89    pub fn clear(&mut self) {
90        self.tree.clear();
91        self.size = 0;
92    }
93
94    pub fn size(&self) -> usize {
95        self.size
96    }
97}
98
99struct WebStorageEnvironment<E: WebStorageEngine> {
100    engine: E,
101    data: OriginEntry,
102}
103
104impl<E: WebStorageEngine> MallocSizeOf for WebStorageEnvironment<E> {
105    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
106        self.data.size_of(ops)
107    }
108}
109
110impl<E: WebStorageEngine> WebStorageEnvironment<E> {
111    fn new(engine: E) -> Self {
112        WebStorageEnvironment {
113            data: engine.load().unwrap_or_default(),
114            engine,
115        }
116    }
117
118    fn clear(&mut self) {
119        self.data.clear();
120        let _ = self.engine.clear();
121    }
122
123    fn delete(&mut self, key: &str) {
124        let _ = self.engine.delete(key);
125    }
126
127    fn set(&mut self, key: &str, value: &str) {
128        let _ = self.engine.set(key, value);
129    }
130}
131
132impl<E: WebStorageEngine> Drop for WebStorageEnvironment<E> {
133    fn drop(&mut self) {
134        self.engine.save(&self.data);
135    }
136}
137
138struct WebStorageManager {
139    port: GenericReceiver<WebStorageThreadMsg>,
140    session_data: FxHashMap<WebViewId, FxHashMap<ImmutableOrigin, OriginEntry>>,
141    config_dir: Option<PathBuf>,
142    thread_pool: Arc<ThreadPool>,
143    environments: FxHashMap<ImmutableOrigin, WebStorageEnvironment<SqliteEngine>>,
144}
145
146impl WebStorageManager {
147    fn new(
148        port: GenericReceiver<WebStorageThreadMsg>,
149        config_dir: Option<PathBuf>,
150    ) -> WebStorageManager {
151        // Uses an estimate of the system cpus to process Webstorage transactions
152        // See https://doc.rust-lang.org/stable/std/thread/fn.available_parallelism.html
153        // If no information can be obtained about the system, uses 4 threads as a default
154        let thread_count = thread::available_parallelism()
155            .map(|i| i.get())
156            .unwrap_or(pref!(threadpools_fallback_worker_num) as usize)
157            .min(pref!(threadpools_webstorage_workers_max).max(1) as usize);
158        WebStorageManager {
159            port,
160            session_data: FxHashMap::default(),
161            config_dir,
162            thread_pool: Arc::new(ThreadPool::new(thread_count, "WebStorage".to_string())),
163            environments: FxHashMap::default(),
164        }
165    }
166}
167
168impl WebStorageManager {
169    fn start(&mut self) {
170        loop {
171            match self.port.recv().unwrap() {
172                WebStorageThreadMsg::Length(sender, storage_type, webview_id, url) => {
173                    self.length(sender, storage_type, webview_id, url)
174                },
175                WebStorageThreadMsg::Key(sender, storage_type, webview_id, url, index) => {
176                    self.key(sender, storage_type, webview_id, url, index)
177                },
178                WebStorageThreadMsg::Keys(sender, storage_type, webview_id, url) => {
179                    self.keys(sender, storage_type, webview_id, url)
180                },
181                WebStorageThreadMsg::SetItem(
182                    sender,
183                    storage_type,
184                    webview_id,
185                    url,
186                    name,
187                    value,
188                ) => {
189                    self.set_item(sender, storage_type, webview_id, url, name, value);
190                },
191                WebStorageThreadMsg::GetItem(sender, storage_type, webview_id, url, name) => {
192                    self.request_item(sender, storage_type, webview_id, url, name)
193                },
194                WebStorageThreadMsg::RemoveItem(sender, storage_type, webview_id, url, name) => {
195                    self.remove_item(sender, storage_type, webview_id, url, name);
196                },
197                WebStorageThreadMsg::Clear(sender, storage_type, webview_id, url) => {
198                    self.clear(sender, storage_type, webview_id, url);
199                },
200                WebStorageThreadMsg::Clone {
201                    sender,
202                    src: src_webview_id,
203                    dest: dest_webview_id,
204                } => {
205                    self.clone(src_webview_id, dest_webview_id);
206                    let _ = sender.send(());
207                },
208                WebStorageThreadMsg::CollectMemoryReport(sender) => {
209                    let reports = self.collect_memory_reports();
210                    sender.send(ProcessReports::new(reports));
211                },
212                WebStorageThreadMsg::Exit(sender) => {
213                    // Nothing to do since we save localstorage set eagerly.
214                    let _ = sender.send(());
215                    break;
216                },
217            }
218        }
219    }
220
221    fn collect_memory_reports(&self) -> Vec<Report> {
222        let mut reports = vec![];
223        perform_memory_report(|ops| {
224            reports.push(Report {
225                path: path!["storage", "local"],
226                kind: ReportKind::ExplicitJemallocHeapSize,
227                size: self.environments.size_of(ops),
228            });
229
230            reports.push(Report {
231                path: path!["storage", "session"],
232                kind: ReportKind::ExplicitJemallocHeapSize,
233                size: self.session_data.size_of(ops),
234            });
235        });
236        reports
237    }
238
239    fn get_origin_location(&self, origin: &ImmutableOrigin) -> Option<PathBuf> {
240        match &self.config_dir {
241            Some(config_dir) => {
242                const NAMESPACE_SERVO_WEBSTORAGE: &uuid::Uuid = &Uuid::from_bytes([
243                    0x37, 0x9e, 0x56, 0xb0, 0x1a, 0x76, 0x44, 0xc5, 0xa4, 0xdb, 0xe2, 0x18, 0xc5,
244                    0xc8, 0xa3, 0x5d,
245                ]);
246                let origin_uuid = Uuid::new_v5(
247                    NAMESPACE_SERVO_WEBSTORAGE,
248                    origin.ascii_serialization().as_bytes(),
249                );
250                Some(config_dir.join("webstorage").join(origin_uuid.to_string()))
251            },
252            None => None,
253        }
254    }
255
256    fn get_environment(
257        &mut self,
258        origin: &ImmutableOrigin,
259    ) -> &WebStorageEnvironment<SqliteEngine> {
260        if self.environments.contains_key(origin) {
261            return self.environments.get(origin).unwrap();
262        }
263
264        let origin_location = self.get_origin_location(origin);
265
266        let engine = SqliteEngine::new(&origin_location, self.thread_pool.clone()).unwrap();
267        let environment = WebStorageEnvironment::new(engine);
268        self.environments.insert(origin.clone(), environment);
269        self.environments.get(origin).unwrap()
270    }
271
272    fn get_environment_mut(
273        &mut self,
274        origin: &ImmutableOrigin,
275    ) -> &mut WebStorageEnvironment<SqliteEngine> {
276        if self.environments.contains_key(origin) {
277            return self.environments.get_mut(origin).unwrap();
278        }
279
280        let origin_location = self.get_origin_location(origin);
281
282        let engine = SqliteEngine::new(&origin_location, self.thread_pool.clone()).unwrap();
283        let environment = WebStorageEnvironment::new(engine);
284        self.environments.insert(origin.clone(), environment);
285        self.environments.get_mut(origin).unwrap()
286    }
287
288    fn select_data(
289        &mut self,
290        storage_type: StorageType,
291        webview_id: WebViewId,
292        origin: ImmutableOrigin,
293    ) -> Option<&OriginEntry> {
294        match storage_type {
295            StorageType::Session => self
296                .session_data
297                .get(&webview_id)
298                .and_then(|origin_map| origin_map.get(&origin)),
299            StorageType::Local => Some(&self.get_environment(&origin).data),
300        }
301    }
302
303    fn select_data_mut(
304        &mut self,
305        storage_type: StorageType,
306        webview_id: WebViewId,
307        origin: ImmutableOrigin,
308    ) -> Option<&mut OriginEntry> {
309        match storage_type {
310            StorageType::Session => self
311                .session_data
312                .get_mut(&webview_id)
313                .and_then(|origin_map| origin_map.get_mut(&origin)),
314            StorageType::Local => Some(&mut self.get_environment_mut(&origin).data),
315        }
316    }
317
318    fn ensure_data_mut(
319        &mut self,
320        storage_type: StorageType,
321        webview_id: WebViewId,
322        origin: ImmutableOrigin,
323    ) -> &mut OriginEntry {
324        match storage_type {
325            StorageType::Session => self
326                .session_data
327                .entry(webview_id)
328                .or_default()
329                .entry(origin)
330                .or_default(),
331            StorageType::Local => &mut self.get_environment_mut(&origin).data,
332        }
333    }
334
335    fn length(
336        &mut self,
337        sender: GenericSender<usize>,
338        storage_type: StorageType,
339        webview_id: WebViewId,
340        url: ServoUrl,
341    ) {
342        let data = self.select_data(storage_type, webview_id, url.origin());
343        sender
344            .send(data.map_or(0, |entry| entry.inner().len()))
345            .unwrap();
346    }
347
348    fn key(
349        &mut self,
350        sender: GenericSender<Option<String>>,
351        storage_type: StorageType,
352        webview_id: WebViewId,
353        url: ServoUrl,
354        index: u32,
355    ) {
356        let data = self.select_data(storage_type, webview_id, url.origin());
357        let key = data
358            .and_then(|entry| entry.inner().keys().nth(index as usize))
359            .cloned();
360        sender.send(key).unwrap();
361    }
362
363    fn keys(
364        &mut self,
365        sender: GenericSender<Vec<String>>,
366        storage_type: StorageType,
367        webview_id: WebViewId,
368        url: ServoUrl,
369    ) {
370        let data = self.select_data(storage_type, webview_id, url.origin());
371        let keys = data.map_or(vec![], |entry| entry.inner().keys().cloned().collect());
372
373        sender.send(keys).unwrap();
374    }
375
376    /// Sends Ok(changed, Some(old_value)) in case there was a previous
377    /// value with the same key name but with different value name
378    /// otherwise sends Err(()) to indicate that the operation would result in
379    /// exceeding the quota limit
380    fn set_item(
381        &mut self,
382        sender: GenericSender<Result<(bool, Option<String>), ()>>,
383        storage_type: StorageType,
384        webview_id: WebViewId,
385        url: ServoUrl,
386        name: String,
387        value: String,
388    ) {
389        let (this_storage_size, other_storage_size) = {
390            let local_data = self.select_data(StorageType::Local, webview_id, url.origin());
391            let local_data_size = local_data.map_or(0, OriginEntry::size);
392            let session_data = self.select_data(StorageType::Session, webview_id, url.origin());
393            let session_data_size = session_data.map_or(0, OriginEntry::size);
394            match storage_type {
395                StorageType::Local => (local_data_size, session_data_size),
396                StorageType::Session => (session_data_size, local_data_size),
397            }
398        };
399
400        let entry = self.ensure_data_mut(storage_type, webview_id, url.origin());
401
402        let mut new_total_size = this_storage_size + value.len();
403        if let Some(old_value) = entry.inner().get(&name) {
404            new_total_size -= old_value.len();
405        } else {
406            new_total_size += name.len();
407        }
408
409        let message = if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT {
410            Err(())
411        } else {
412            let result =
413                entry
414                    .insert(name.clone(), value.clone())
415                    .map_or(Ok((true, None)), |old| {
416                        if old == value {
417                            Ok((false, None))
418                        } else {
419                            Ok((true, Some(old)))
420                        }
421                    });
422            let env = self.get_environment_mut(&url.origin());
423            env.set(&name, &value);
424            result
425        };
426        sender.send(message).unwrap();
427    }
428
429    fn request_item(
430        &mut self,
431        sender: GenericSender<Option<String>>,
432        storage_type: StorageType,
433        webview_id: WebViewId,
434        url: ServoUrl,
435        name: String,
436    ) {
437        let data = self.select_data(storage_type, webview_id, url.origin());
438        sender
439            .send(data.and_then(|entry| entry.inner().get(&name)).cloned())
440            .unwrap();
441    }
442
443    /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None
444    fn remove_item(
445        &mut self,
446        sender: GenericSender<Option<String>>,
447        storage_type: StorageType,
448        webview_id: WebViewId,
449        url: ServoUrl,
450        name: String,
451    ) {
452        let data = self.select_data_mut(storage_type, webview_id, url.origin());
453        let old_value = data.and_then(|entry| entry.remove(&name));
454        sender.send(old_value).unwrap();
455        let env = self.get_environment_mut(&url.origin());
456        env.delete(&name);
457    }
458
459    fn clear(
460        &mut self,
461        sender: GenericSender<bool>,
462        storage_type: StorageType,
463        webview_id: WebViewId,
464        url: ServoUrl,
465    ) {
466        let data = self.select_data_mut(storage_type, webview_id, url.origin());
467        sender
468            .send(data.is_some_and(|entry| {
469                if !entry.inner().is_empty() {
470                    entry.clear();
471                    true
472                } else {
473                    false
474                }
475            }))
476            .unwrap();
477        let env = self.get_environment_mut(&url.origin());
478        env.clear();
479    }
480
481    fn clone(&mut self, src_webview_id: WebViewId, dest_webview_id: WebViewId) {
482        let Some(src_origin_entries) = self.session_data.get(&src_webview_id) else {
483            return;
484        };
485
486        let dest_origin_entries = src_origin_entries.clone();
487        self.session_data
488            .insert(dest_webview_id, dest_origin_entries);
489    }
490}