| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ |
| |
| use std::borrow::ToOwned; |
| use std::collections::{BTreeMap, HashMap}; |
| use std::path::PathBuf; |
| use std::thread; |
| |
| use base::generic_channel::{self, GenericReceiver, GenericSender}; |
| use base::id::WebViewId; |
| use malloc_size_of::MallocSizeOf; |
| use net_traits::storage_thread::{StorageThreadMsg, StorageType}; |
| use profile_traits::mem::{ |
| ProcessReports, ProfilerChan as MemProfilerChan, Report, ReportKind, perform_memory_report, |
| }; |
| use profile_traits::path; |
| use servo_url::ServoUrl; |
| |
| use crate::resource_thread; |
| |
| const QUOTA_SIZE_LIMIT: usize = 5 * 1024 * 1024; |
| |
| pub trait StorageThreadFactory { |
| fn new(config_dir: Option<PathBuf>, mem_profiler_chan: MemProfilerChan) -> Self; |
| } |
| |
| impl StorageThreadFactory for GenericSender<StorageThreadMsg> { |
| /// Create a storage thread |
| fn new( |
| config_dir: Option<PathBuf>, |
| mem_profiler_chan: MemProfilerChan, |
| ) -> GenericSender<StorageThreadMsg> { |
| let (chan, port) = generic_channel::channel().unwrap(); |
| let chan2 = chan.clone(); |
| thread::Builder::new() |
| .name("StorageManager".to_owned()) |
| .spawn(move || { |
| mem_profiler_chan.run_with_memory_reporting( |
| || StorageManager::new(port, config_dir).start(), |
| String::from("storage-reporter"), |
| chan2, |
| StorageThreadMsg::CollectMemoryReport, |
| ); |
| }) |
| .expect("Thread spawning failed"); |
| chan |
| } |
| } |
| |
| type OriginEntry = (usize, BTreeMap<String, String>); |
| |
| struct StorageManager { |
| port: GenericReceiver<StorageThreadMsg>, |
| session_data: HashMap<WebViewId, HashMap<String, OriginEntry>>, |
| local_data: HashMap<String, OriginEntry>, |
| config_dir: Option<PathBuf>, |
| } |
| |
| impl StorageManager { |
| fn new(port: GenericReceiver<StorageThreadMsg>, config_dir: Option<PathBuf>) -> StorageManager { |
| let mut local_data = HashMap::new(); |
| if let Some(ref config_dir) = config_dir { |
| resource_thread::read_json_from_file(&mut local_data, config_dir, "local_data.json"); |
| } |
| StorageManager { |
| port, |
| session_data: HashMap::new(), |
| local_data, |
| config_dir, |
| } |
| } |
| } |
| |
| impl StorageManager { |
| fn start(&mut self) { |
| loop { |
| match self.port.recv().unwrap() { |
| StorageThreadMsg::Length(sender, storage_type, webview_id, url) => { |
| self.length(sender, storage_type, webview_id, url) |
| }, |
| StorageThreadMsg::Key(sender, storage_type, webview_id, url, index) => { |
| self.key(sender, storage_type, webview_id, url, index) |
| }, |
| StorageThreadMsg::Keys(sender, storage_type, webview_id, url) => { |
| self.keys(sender, storage_type, webview_id, url) |
| }, |
| StorageThreadMsg::SetItem(sender, storage_type, webview_id, url, name, value) => { |
| self.set_item(sender, storage_type, webview_id, url, name, value); |
| self.save_state() |
| }, |
| StorageThreadMsg::GetItem(sender, storage_type, webview_id, url, name) => { |
| self.request_item(sender, storage_type, webview_id, url, name) |
| }, |
| StorageThreadMsg::RemoveItem(sender, storage_type, webview_id, url, name) => { |
| self.remove_item(sender, storage_type, webview_id, url, name); |
| self.save_state() |
| }, |
| StorageThreadMsg::Clear(sender, storage_type, webview_id, url) => { |
| self.clear(sender, storage_type, webview_id, url); |
| self.save_state() |
| }, |
| StorageThreadMsg::Clone { |
| sender, |
| src: src_webview_id, |
| dest: dest_webview_id, |
| } => { |
| self.clone(src_webview_id, dest_webview_id); |
| let _ = sender.send(()); |
| }, |
| StorageThreadMsg::CollectMemoryReport(sender) => { |
| let reports = self.collect_memory_reports(); |
| sender.send(ProcessReports::new(reports)); |
| }, |
| StorageThreadMsg::Exit(sender) => { |
| // Nothing to do since we save localstorage set eagerly. |
| let _ = sender.send(()); |
| break; |
| }, |
| } |
| } |
| } |
| |
| fn collect_memory_reports(&self) -> Vec<Report> { |
| let mut reports = vec![]; |
| perform_memory_report(|ops| { |
| reports.push(Report { |
| path: path!["storage", "local"], |
| kind: ReportKind::ExplicitJemallocHeapSize, |
| size: self.local_data.size_of(ops), |
| }); |
| |
| reports.push(Report { |
| path: path!["storage", "session"], |
| kind: ReportKind::ExplicitJemallocHeapSize, |
| size: self.session_data.size_of(ops), |
| }); |
| }); |
| reports |
| } |
| |
| fn save_state(&self) { |
| if let Some(ref config_dir) = self.config_dir { |
| resource_thread::write_json_to_file(&self.local_data, config_dir, "local_data.json"); |
| } |
| } |
| |
| fn select_data( |
| &self, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| origin: &str, |
| ) -> Option<&OriginEntry> { |
| match storage_type { |
| StorageType::Session => self |
| .session_data |
| .get(&webview_id) |
| .and_then(|origin_map| origin_map.get(origin)), |
| StorageType::Local => self.local_data.get(origin), |
| } |
| } |
| |
| fn select_data_mut( |
| &mut self, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| origin: &str, |
| ) -> Option<&mut OriginEntry> { |
| match storage_type { |
| StorageType::Session => self |
| .session_data |
| .get_mut(&webview_id) |
| .and_then(|origin_map| origin_map.get_mut(origin)), |
| StorageType::Local => self.local_data.get_mut(origin), |
| } |
| } |
| |
| fn ensure_data_mut( |
| &mut self, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| origin: &str, |
| ) -> &mut OriginEntry { |
| match storage_type { |
| StorageType::Session => self |
| .session_data |
| .entry(webview_id) |
| .or_default() |
| .entry(origin.to_string()) |
| .or_default(), |
| StorageType::Local => self.local_data.entry(origin.to_string()).or_default(), |
| } |
| } |
| |
| fn length( |
| &self, |
| sender: GenericSender<usize>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| ) { |
| let origin = self.origin_as_string(url); |
| let data = self.select_data(storage_type, webview_id, &origin); |
| sender |
| .send(data.map_or(0, |(_, entry)| entry.len())) |
| .unwrap(); |
| } |
| |
| fn key( |
| &self, |
| sender: GenericSender<Option<String>>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| index: u32, |
| ) { |
| let origin = self.origin_as_string(url); |
| let data = self.select_data(storage_type, webview_id, &origin); |
| let key = data |
| .and_then(|(_, entry)| entry.keys().nth(index as usize)) |
| .cloned(); |
| sender.send(key).unwrap(); |
| } |
| |
| fn keys( |
| &self, |
| sender: GenericSender<Vec<String>>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| ) { |
| let origin = self.origin_as_string(url); |
| let data = self.select_data(storage_type, webview_id, &origin); |
| let keys = data.map_or(vec![], |(_, entry)| entry.keys().cloned().collect()); |
| |
| sender.send(keys).unwrap(); |
| } |
| |
| /// Sends Ok(changed, Some(old_value)) in case there was a previous |
| /// value with the same key name but with different value name |
| /// otherwise sends Err(()) to indicate that the operation would result in |
| /// exceeding the quota limit |
| fn set_item( |
| &mut self, |
| sender: GenericSender<Result<(bool, Option<String>), ()>>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| name: String, |
| value: String, |
| ) { |
| let origin = self.origin_as_string(url); |
| |
| let (this_storage_size, other_storage_size) = { |
| let local_data = self.select_data(StorageType::Local, webview_id, &origin); |
| let session_data = self.select_data(StorageType::Session, webview_id, &origin); |
| let local_data_size = local_data.map_or(0, |&(total, _)| total); |
| let session_data_size = session_data.map_or(0, |&(total, _)| total); |
| match storage_type { |
| StorageType::Local => (local_data_size, session_data_size), |
| StorageType::Session => (session_data_size, local_data_size), |
| } |
| }; |
| |
| let &mut (ref mut total, ref mut entry) = |
| self.ensure_data_mut(storage_type, webview_id, &origin); |
| |
| let mut new_total_size = this_storage_size + value.len(); |
| if let Some(old_value) = entry.get(&name) { |
| new_total_size -= old_value.len(); |
| } else { |
| new_total_size += name.len(); |
| } |
| |
| let message = if (new_total_size + other_storage_size) > QUOTA_SIZE_LIMIT { |
| Err(()) |
| } else { |
| *total = new_total_size; |
| entry |
| .insert(name.clone(), value.clone()) |
| .map_or(Ok((true, None)), |old| { |
| if old == value { |
| Ok((false, None)) |
| } else { |
| Ok((true, Some(old))) |
| } |
| }) |
| }; |
| sender.send(message).unwrap(); |
| } |
| |
| fn request_item( |
| &self, |
| sender: GenericSender<Option<String>>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| name: String, |
| ) { |
| let origin = self.origin_as_string(url); |
| let data = self.select_data(storage_type, webview_id, &origin); |
| sender |
| .send(data.and_then(|(_, entry)| entry.get(&name)).cloned()) |
| .unwrap(); |
| } |
| |
| /// Sends Some(old_value) in case there was a previous value with the key name, otherwise sends None |
| fn remove_item( |
| &mut self, |
| sender: GenericSender<Option<String>>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| name: String, |
| ) { |
| let origin = self.origin_as_string(url); |
| let data = self.select_data_mut(storage_type, webview_id, &origin); |
| let old_value = data.and_then(|&mut (ref mut total, ref mut entry)| { |
| entry.remove(&name).inspect(|old| { |
| *total -= name.len() + old.len(); |
| }) |
| }); |
| sender.send(old_value).unwrap(); |
| } |
| |
| fn clear( |
| &mut self, |
| sender: GenericSender<bool>, |
| storage_type: StorageType, |
| webview_id: WebViewId, |
| url: ServoUrl, |
| ) { |
| let origin = self.origin_as_string(url); |
| let data = self.select_data_mut(storage_type, webview_id, &origin); |
| sender |
| .send(data.is_some_and(|&mut (ref mut total, ref mut entry)| { |
| if !entry.is_empty() { |
| entry.clear(); |
| *total = 0; |
| true |
| } else { |
| false |
| } |
| })) |
| .unwrap(); |
| } |
| |
| fn clone(&mut self, src_webview_id: WebViewId, dest_webview_id: WebViewId) { |
| let Some(src_origin_entries) = self.session_data.get(&src_webview_id) else { |
| return; |
| }; |
| |
| let dest_origin_entries = src_origin_entries.clone(); |
| self.session_data |
| .insert(dest_webview_id, dest_origin_entries); |
| } |
| |
| fn origin_as_string(&self, url: ServoUrl) -> String { |
| url.origin().ascii_serialization() |
| } |
| } |