use std::cell::{Cell, RefCell};
use bitflags::bitflags;
use cookie::Cookie;
use log::warn;
use net_traits::pub_domains::registered_domain_name;
use net_traits::{CookieOperationId, ResourceThreads, SiteDescriptor};
use rustc_hash::FxHashMap;
use servo_url::ServoUrl;
use storage_traits::StorageThreads;
use storage_traits::webstorage_thread::{OriginDescriptor, WebStorageType};
use url::Url;
use crate::CookieSource;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct StorageType: u8 {
const Cookies = 1 << 0;
const Local = 1 << 1;
const Session = 1 << 2;
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SiteData {
name: String,
storage_types: StorageType,
}
impl SiteData {
pub fn new(name: impl Into<String>, storage_types: StorageType) -> SiteData {
SiteData {
name: name.into(),
storage_types,
}
}
pub fn name(&self) -> String {
self.name.clone()
}
pub fn storage_types(&self) -> StorageType {
self.storage_types
}
}
pub(crate) enum CookieOperationResponse {
Cookies(Vec<Cookie<'static>>),
Done,
}
enum CookieOperationCallback {
Cookies(Box<dyn FnOnce(Vec<Cookie<'static>>)>),
Done(Box<dyn FnOnce()>),
}
pub struct SiteDataManager {
public_resource_threads: ResourceThreads,
private_resource_threads: ResourceThreads,
public_storage_threads: StorageThreads,
private_storage_threads: StorageThreads,
next_cookie_op_id: Cell<u64>,
pending_cookie_callbacks: RefCell<FxHashMap<CookieOperationId, CookieOperationCallback>>,
}
impl SiteDataManager {
pub(crate) fn new(
public_resource_threads: ResourceThreads,
private_resource_threads: ResourceThreads,
public_storage_threads: StorageThreads,
private_storage_threads: StorageThreads,
) -> Self {
Self {
public_resource_threads,
private_resource_threads,
public_storage_threads,
private_storage_threads,
next_cookie_op_id: Cell::new(0),
pending_cookie_callbacks: RefCell::new(FxHashMap::default()),
}
}
pub fn site_data(&self, storage_types: StorageType) -> Vec<SiteData> {
let mut all_sites: FxHashMap<String, StorageType> = FxHashMap::default();
let mut add_sites = |sites: Vec<SiteDescriptor>, storage_type: StorageType| {
for site in sites {
all_sites
.entry(site.name)
.and_modify(|storage_types| *storage_types |= storage_type)
.or_insert(storage_type);
}
};
if storage_types.contains(StorageType::Cookies) {
let public_cookies = self.public_resource_threads.cookies();
add_sites(public_cookies, StorageType::Cookies);
let private_cookies = self.private_resource_threads.cookies();
add_sites(private_cookies, StorageType::Cookies);
}
let mut add_origins = |origins: Vec<OriginDescriptor>, storage_type: StorageType| {
for origin in origins {
let url =
ServoUrl::parse(&origin.name).expect("Should always be able to parse origins.");
let Some(domain) = registered_domain_name(&url) else {
warn!("Failed to get a registered domain name for: {url}.");
continue;
};
let domain = domain.to_string();
all_sites
.entry(domain)
.and_modify(|storage_types| *storage_types |= storage_type)
.or_insert(storage_type);
}
};
if storage_types.contains(StorageType::Local) {
let public_origins = self
.public_storage_threads
.webstorage_origins(WebStorageType::Local);
add_origins(public_origins, StorageType::Local);
let private_origins = self
.private_storage_threads
.webstorage_origins(WebStorageType::Local);
add_origins(private_origins, StorageType::Local);
}
if storage_types.contains(StorageType::Session) {
let public_origins = self
.public_storage_threads
.webstorage_origins(WebStorageType::Session);
add_origins(public_origins, StorageType::Session);
let private_origins = self
.private_storage_threads
.webstorage_origins(WebStorageType::Session);
add_origins(private_origins, StorageType::Session);
}
let mut result: Vec<SiteData> = all_sites
.into_iter()
.map(|(name, storage_types)| SiteData::new(name, storage_types))
.collect();
result.sort_by_key(SiteData::name);
result
}
pub fn clear_site_data(&self, sites: &[&str], storage_types: StorageType) {
if storage_types.contains(StorageType::Cookies) {
self.public_resource_threads.clear_cookies_for_sites(sites);
self.private_resource_threads.clear_cookies_for_sites(sites);
}
if storage_types.contains(StorageType::Local) {
self.public_storage_threads
.clear_webstorage_for_sites(WebStorageType::Local, sites);
self.private_storage_threads
.clear_webstorage_for_sites(WebStorageType::Local, sites);
}
if storage_types.contains(StorageType::Session) {
self.public_storage_threads
.clear_webstorage_for_sites(WebStorageType::Session, sites);
self.private_storage_threads
.clear_webstorage_for_sites(WebStorageType::Session, sites);
}
}
pub fn clear_cookies(&self) {
self.public_resource_threads.clear_cookies();
self.private_resource_threads.clear_cookies();
}
pub fn clear_session_cookies(&self) {
self.public_resource_threads.clear_session_cookies();
self.private_resource_threads.clear_session_cookies();
}
pub fn cookies_for_url(&self, url: Url, source: CookieSource) -> Vec<Cookie<'static>> {
self.public_resource_threads
.cookies_for_url(url.into(), source)
}
pub fn set_cookie_for_url(&self, url: Url, cookie: Cookie<'static>) {
self.public_resource_threads.set_cookie_for_url_sync(
url.into(),
cookie,
CookieSource::HTTP,
);
}
pub fn cookies_for_url_async(
&self,
url: Url,
source: CookieSource,
callback: impl FnOnce(Vec<Cookie<'static>>) + 'static,
) {
let id = self.next_operation_id();
self.pending_cookie_callbacks
.borrow_mut()
.insert(id, CookieOperationCallback::Cookies(Box::new(callback)));
self.public_resource_threads
.cookies_for_url_async(id, url.into(), source);
}
pub fn set_cookie_for_url_async(
&self,
url: Url,
cookie: Cookie<'static>,
callback: impl FnOnce() + 'static,
) {
let id = self.next_operation_id();
self.pending_cookie_callbacks
.borrow_mut()
.insert(id, CookieOperationCallback::Done(Box::new(callback)));
self.public_resource_threads.set_cookie_for_url_async(
id,
url.into(),
cookie,
CookieSource::HTTP,
);
}
pub(crate) fn handle_cookie_response(
&self,
id: CookieOperationId,
response: CookieOperationResponse,
) {
let Some(callback) = self.pending_cookie_callbacks.borrow_mut().remove(&id) else {
warn!("Received cookie response for unknown operation {id:?}");
return;
};
match (callback, response) {
(CookieOperationCallback::Cookies(cb), CookieOperationResponse::Cookies(cookies)) => {
cb(cookies);
},
(CookieOperationCallback::Done(cb), CookieOperationResponse::Done) => {
cb();
},
_ => {
warn!("Cookie response type mismatch for operation {id:?}");
},
}
}
fn next_operation_id(&self) -> CookieOperationId {
let id = CookieOperationId(self.next_cookie_op_id.get());
self.next_cookie_op_id.set(id.0 + 1);
id
}
}