use super::{stream_resource_handler::StreamResourceHandler, zip_archive::*};
use crate::*;
use std::{
collections::{BTreeMap, VecDeque},
path::PathBuf,
sync::{Arc, Mutex, Weak},
};
pub type UrlFilter = Box<dyn Send + Sync + Fn(&str) -> String>;
pub type MimeTypeResolver = Box<dyn Send + Sync + Fn(&str) -> String>;
struct RequestParams {
url: String,
browser: Browser,
frame: Frame,
request: Request,
url_filter: Arc<UrlFilter>,
mime_type_resolver: Arc<MimeTypeResolver>,
}
#[derive(Default)]
struct ProviderEntry {
provider: Option<Box<dyn ResourceManagerProvider>>,
order: i32,
identifier: String,
pending_requests: VecDeque<Arc<Mutex<ResourceManagerRequest>>>,
deletion_pending: bool,
}
struct RequestState {
manager: Weak<Mutex<ResourceManager>>,
callback: Option<Callback>,
current_entry: usize,
current_request: usize,
params: Arc<RequestParams>,
}
impl Drop for RequestState {
fn drop(&mut self) {
if let Some(callback) = self.callback.take() {
callback.cont();
}
}
}
pub struct ResourceManagerRequest {
weak_self: Weak<Mutex<Self>>,
state: Option<RequestState>,
params: Arc<RequestParams>,
}
impl ResourceManagerRequest {
fn new(state: Option<RequestState>, params: Arc<RequestParams>) -> Arc<Mutex<Self>> {
Arc::new_cyclic(|weak_self| {
Mutex::new(Self {
weak_self: weak_self.clone(),
state,
params,
})
})
}
pub fn url(&self) -> &str {
&self.params.url
}
pub fn browser(&self) -> &Browser {
&self.params.browser
}
pub fn frame(&self) -> &Frame {
&self.params.frame
}
pub fn request(&self) -> &Request {
&self.params.request
}
pub fn url_filter(&self) -> &UrlFilter {
&self.params.url_filter
}
pub fn mime_type_resolver(&self) -> &MimeTypeResolver {
&self.params.mime_type_resolver
}
pub fn continue_request(&mut self, handler: Option<ResourceHandler>) {
let Some(state) = self.state.take() else {
return;
};
let io_thread_id = ThreadId::IO;
let mut task = ContinueRequest::new(Arc::new(Mutex::new(Some(state))), handler);
post_task(io_thread_id, Some(&mut task));
}
pub fn stop_request(&mut self) {
let Some(state) = self.state.take() else {
return;
};
let io_thread_id = ThreadId::IO;
let mut task = StopRequest::new(Arc::new(Mutex::new(Some(state))));
post_task(io_thread_id, Some(&mut task));
}
fn send_request(&mut self) -> Option<RequestState> {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"send_request must be called on the IO thread"
);
let state = self.state.as_ref()?;
let Some(manager) = state.manager.upgrade() else {
return self.state.take();
};
let Ok(mut manager) = manager.lock() else {
return self.state.take();
};
let Some(provider_entry) = manager.providers.get_mut(state.current_entry) else {
return self.state.take();
};
let Some(provider) = provider_entry.provider.as_mut() else {
return self.state.take();
};
let Some(request) = self.weak_self.upgrade() else {
return self.state.take();
};
if !provider.on_request(request) {
return self.state.take();
}
None
}
}
wrap_task! {
struct ContinueRequest {
state: Arc<Mutex<Option<RequestState>>>,
handler: Option<ResourceHandler>,
}
impl Task {
fn execute(&self) {
let Ok(mut state) = self.state.lock() else {
return;
};
let Some(state) = state.take() else {
return;
};
let Some(manager) = state.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
manager.continue_request(state, self.handler.clone());
}
}
}
wrap_task! {
struct StopRequest {
state: Arc<Mutex<Option<RequestState>>>,
}
impl Task {
fn execute(&self) {
let Ok(mut state) = self.state.lock() else {
return;
};
let Some(state) = state.take() else {
return;
};
let Some(manager) = state.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
manager.stop_request(state);
}
}
}
pub trait ResourceManagerProvider: Send {
fn on_request(&self, request: Arc<Mutex<ResourceManagerRequest>>) -> bool;
fn on_request_canceled(&self, _request: Arc<Mutex<ResourceManagerRequest>>) {}
}
struct ContentProvider {
url: String,
content: String,
mime_type: String,
}
impl ContentProvider {
fn new_resource_manager_provider(
url: &str,
content: &str,
mime_type: &str,
) -> Box<dyn ResourceManagerProvider> {
Box::new(Self {
url: url.to_string(),
content: content.to_string(),
mime_type: mime_type.to_string(),
})
}
}
impl ResourceManagerProvider for ContentProvider {
fn on_request(&self, request: Arc<Mutex<ResourceManagerRequest>>) -> bool {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"on_request must be called on the IO thread"
);
let Ok(mut request) = request.lock() else {
return false;
};
if request.url() != self.url {
return false;
}
let mut data: Vec<_> = self.content.bytes().collect();
let Some(stream) = stream_reader_create_for_data(data.as_mut_ptr(), data.len()) else {
return false;
};
let mime_type = if self.mime_type.is_empty() {
self.mime_type.clone()
} else {
(request.mime_type_resolver())(&self.url)
};
request.continue_request(Some(StreamResourceHandler::new_with_stream(
mime_type, stream,
)));
true
}
}
struct DirectoryProvider {
url_path: String,
directory_path: PathBuf,
}
impl DirectoryProvider {
fn new_resource_manager_provider(
url_path: &str,
directory_path: PathBuf,
) -> Box<dyn ResourceManagerProvider> {
Box::new(Self {
url_path: normalize_url_path(url_path),
directory_path,
})
}
}
impl ResourceManagerProvider for DirectoryProvider {
fn on_request(&self, request: Arc<Mutex<ResourceManagerRequest>>) -> bool {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"on_request must be called on the IO thread"
);
let Some(file_path) = ({
let Ok(request) = request.lock() else {
return false;
};
let url = request.url();
if !url.starts_with(self.url_path.as_str()) {
return false;
}
get_file_relative_path(self.directory_path.clone(), self.url_path.as_str(), url)
}) else {
return false;
};
let mut task = OpenFileRequest::new(file_path, request);
post_task(ThreadId::FILE_USER_BLOCKING, Some(&mut task));
true
}
}
wrap_task! {
struct OpenFileRequest {
file_path: PathBuf,
request: Arc<Mutex<ResourceManagerRequest>>,
}
impl Task {
fn execute(&self) {
debug_assert_ne!(
currently_on(ThreadId::FILE_USER_BLOCKING),
0,
"execute must be called on the file thread"
);
let file_path = self.file_path.display().to_string();
let Some(stream) =
stream_reader_create_for_file(Some(&CefString::from(file_path.as_str())))
else {
return;
};
let mut task = ContinueFileRequest::new(stream, self.request.clone());
post_task(ThreadId::IO, Some(&mut task));
}
}
}
wrap_task! {
struct ContinueFileRequest {
stream: StreamReader,
request: Arc<Mutex<ResourceManagerRequest>>,
}
impl Task {
fn execute(&self) {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"execute must be called on the IO thread"
);
let Ok(mut request) = self.request.lock() else {
return;
};
let handler = StreamResourceHandler::new_with_stream(
(request.mime_type_resolver())(request.url()),
self.stream.clone(),
);
request.continue_request(Some(handler));
}
}
}
struct ArchiveProviderState {
weak_self: Weak<Mutex<ArchiveProviderState>>,
url_path: String,
archive_path: PathBuf,
password: String,
load_started: bool,
load_ended: bool,
archive: Option<ZipArchive>,
pending_requests: Vec<Arc<Mutex<ResourceManagerRequest>>>,
}
impl ArchiveProviderState {
fn new(url_path: &str, archive_path: PathBuf, password: &str) -> Arc<Mutex<Self>> {
Arc::new_cyclic(|weak_self| {
Mutex::new(Self {
weak_self: weak_self.clone(),
url_path: normalize_url_path(url_path),
archive_path,
password: password.to_string(),
load_started: false,
load_ended: false,
archive: None,
pending_requests: Default::default(),
})
})
}
fn continue_request(&mut self, request: Arc<Mutex<ResourceManagerRequest>>) -> bool {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"continue_request must be called on the IO thread"
);
let Ok(mut request) = request.lock() else {
return false;
};
let url = request.url();
let (Some(archive), Some(relative_path)) = (
self.archive.as_ref(),
get_file_relative_path(Default::default(), self.url_path.as_str(), url),
) else {
return false;
};
let relative_path = relative_path.display().to_string();
let Some(file) = archive.file(&relative_path) else {
return false;
};
let Ok(file) = file.lock() else {
return false;
};
let Some(stream) = file.stream_reader() else {
return false;
};
let handler =
StreamResourceHandler::new_with_stream((request.mime_type_resolver())(url), stream);
request.continue_request(Some(handler));
true
}
}
struct ArchiveProvider {
state: Arc<Mutex<ArchiveProviderState>>,
}
impl ArchiveProvider {
fn new_resource_manager_provider(
url_path: &str,
archive_path: PathBuf,
password: &str,
) -> Box<dyn ResourceManagerProvider> {
Box::new(Self {
state: ArchiveProviderState::new(url_path, archive_path, password),
})
}
}
impl ResourceManagerProvider for ArchiveProvider {
fn on_request(&self, request: Arc<Mutex<ResourceManagerRequest>>) -> bool {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"on_request must be called on the IO thread"
);
let (Ok(mut state), Ok(request_lock)) = (self.state.lock(), request.lock()) else {
return false;
};
let url = request_lock.url();
if !url.starts_with(state.url_path.as_str()) {
return false;
}
if !state.load_started {
state.load_started = true;
state.pending_requests.push(request.clone());
let mut task = OpenZipRequest::new(state.weak_self.clone());
post_task(ThreadId::FILE_USER_BLOCKING, Some(&mut task));
return true;
}
if state.load_started && !state.load_ended {
state.pending_requests.push(request.clone());
return true;
}
state.continue_request(request.clone())
}
}
wrap_task! {
struct OpenZipRequest {
state: Weak<Mutex<ArchiveProviderState>>,
}
impl Task {
fn execute(&self) {
debug_assert_ne!(
currently_on(ThreadId::FILE_USER_BLOCKING),
0,
"execute must be called on the file thread"
);
let Some(state) = self.state.upgrade() else {
return;
};
let Ok(mut state) = state.lock() else {
return;
};
let file_path = state.archive_path.display().to_string();
let Some(mut stream) =
stream_reader_create_for_file(Some(&CefString::from(file_path.as_str())))
else {
return;
};
let archive = ZipArchive::default();
if archive.load(&mut stream, state.password.as_str(), true) == 0 {
return;
}
state.archive = Some(archive);
let mut task = ContinueZipRequest::new(state.weak_self.clone());
post_task(ThreadId::IO, Some(&mut task));
}
}
}
wrap_task! {
struct ContinueZipRequest {
state: Weak<Mutex<ArchiveProviderState>>,
}
impl Task {
fn execute(&self) {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"execute must be called on the IO thread"
);
let Some(state) = self.state.upgrade() else {
return;
};
let Ok(mut state) = state.lock() else {
return;
};
state.load_ended = true;
for request in std::mem::take(&mut state.pending_requests) {
state.continue_request(request);
}
}
}
}
wrap_task! {
struct AddProvider {
manager: Weak<Mutex<ResourceManager>>,
provider: Arc<Mutex<Option<Box<dyn ResourceManagerProvider>>>>,
order: i32,
identifier: String,
}
impl Task {
fn execute(&self) {
let Some(manager) = self.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
let Ok(mut provider) = self.provider.lock() else {
return;
};
let Some(provider) = provider.take() else {
return;
};
manager.add_provider(provider, self.order, &self.identifier);
}
}
}
wrap_task! {
struct RemoveProviders {
manager: Weak<Mutex<ResourceManager>>,
identifier: String,
}
impl Task {
fn execute(&self) {
let Some(manager) = self.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
manager.remove_providers(&self.identifier);
}
}
}
wrap_task! {
struct RemoveAllProviders {
manager: Weak<Mutex<ResourceManager>>,
}
impl Task {
fn execute(&self) {
let Some(manager) = self.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
manager.remove_all_providers();
}
}
}
wrap_task! {
struct SetUrlFilter {
manager: Weak<Mutex<ResourceManager>>,
filter: Arc<Mutex<Option<Option<UrlFilter>>>>,
}
impl Task {
fn execute(&self) {
let Some(manager) = self.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
let Ok(mut filter) = self.filter.lock() else {
return;
};
let Some(filter) = filter.take() else {
return;
};
manager.set_url_filter(filter);
}
}
}
wrap_task! {
struct SetMimeTypeResolver {
manager: Weak<Mutex<ResourceManager>>,
resolver: Arc<Mutex<Option<Option<MimeTypeResolver>>>>,
}
impl Task {
fn execute(&self) {
let Some(manager) = self.manager.upgrade() else {
return;
};
let Ok(mut manager) = manager.lock() else {
return;
};
let Ok(mut resolver) = self.resolver.lock() else {
return;
};
let Some(resolver) = resolver.take() else {
return;
};
manager.set_mime_type_resolver(resolver);
}
}
}
pub struct ResourceManager {
providers: VecDeque<ProviderEntry>,
pending_handlers: BTreeMap<u64, ResourceHandler>,
mime_type_resolver: Arc<MimeTypeResolver>,
url_filter: Arc<UrlFilter>,
weak_self: Weak<Mutex<Self>>,
}
impl ResourceManager {
pub fn new() -> Arc<Mutex<Self>> {
Arc::new_cyclic(|weak_self| {
Mutex::new(Self {
providers: Default::default(),
pending_handlers: Default::default(),
mime_type_resolver: Arc::new(Box::new(get_mime_type)),
url_filter: Arc::new(Box::new(get_filtered_url)),
weak_self: weak_self.clone(),
})
})
}
pub fn add_content_provider(
&mut self,
url: &str,
content: &str,
mime_type: &str,
order: i32,
identifier: &str,
) {
self.add_provider(
ContentProvider::new_resource_manager_provider(url, content, mime_type),
order,
identifier,
)
}
pub fn add_directory_provider(
&mut self,
url_path: &str,
directory_path: &str,
order: i32,
identifier: &str,
) {
self.add_provider(
DirectoryProvider::new_resource_manager_provider(url_path, directory_path.into()),
order,
identifier,
)
}
pub fn add_archive_provider(
&mut self,
url_path: &str,
archive_path: &str,
password: &str,
order: i32,
identifier: &str,
) {
self.add_provider(
ArchiveProvider::new_resource_manager_provider(url_path, archive_path.into(), password),
order,
identifier,
)
}
pub fn add_provider(
&mut self,
provider: Box<dyn ResourceManagerProvider>,
order: i32,
identifier: &str,
) {
let io_thread_id = ThreadId::IO;
if currently_on(io_thread_id) == 0 {
let mut task = AddProvider::new(
self.weak_self.clone(),
Arc::new(Mutex::new(Some(provider))),
order,
identifier.to_string(),
);
post_task(io_thread_id, Some(&mut task));
return;
}
let provider_entry = ProviderEntry {
provider: Some(provider),
order,
identifier: identifier.to_string(),
..Default::default()
};
if self.providers.is_empty() {
self.providers.push_back(provider_entry);
return;
}
let index = self.providers.partition_point(|entry| entry.order < order);
self.providers.insert(index, provider_entry);
}
pub fn remove_providers(&mut self, identifier: &str) {
let io_thread_id = ThreadId::IO;
if currently_on(io_thread_id) == 0 {
let mut task = RemoveProviders::new(self.weak_self.clone(), identifier.to_string());
post_task(io_thread_id, Some(&mut task));
return;
}
if self.providers.is_empty() {
return;
}
let mut index = 0;
while index < self.providers.len() {
index = if self.providers[index].identifier == identifier {
self.delete_provider(index, false)
} else {
index + 1
};
}
}
pub fn remove_all_providers(&mut self) {
let io_thread_id = ThreadId::IO;
if currently_on(io_thread_id) == 0 {
let mut task = RemoveAllProviders::new(self.weak_self.clone());
post_task(io_thread_id, Some(&mut task));
return;
}
if self.providers.is_empty() {
return;
}
let mut index = 0;
while index < self.providers.len() {
index = self.delete_provider(index, true);
}
}
pub fn set_url_filter(&mut self, filter: Option<UrlFilter>) {
let io_thread_id = ThreadId::IO;
if currently_on(io_thread_id) == 0 {
let mut task =
SetUrlFilter::new(self.weak_self.clone(), Arc::new(Mutex::new(Some(filter))));
post_task(io_thread_id, Some(&mut task));
return;
}
self.url_filter = Arc::new(filter.unwrap_or(Box::new(get_filtered_url)));
}
pub fn set_mime_type_resolver(&mut self, resolver: Option<MimeTypeResolver>) {
let io_thread_id = ThreadId::IO;
if currently_on(io_thread_id) == 0 {
let mut task = SetMimeTypeResolver::new(
self.weak_self.clone(),
Arc::new(Mutex::new(Some(resolver))),
);
post_task(io_thread_id, Some(&mut task));
return;
}
self.mime_type_resolver = Arc::new(resolver.unwrap_or(Box::new(get_mime_type)));
}
pub fn on_before_resource_load(
&mut self,
browser: Browser,
frame: Frame,
request: Request,
callback: Callback,
) -> ReturnValue {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"on_before_resource_load must be called on the IO thread"
);
let first_entry = self.get_next_valid_provider(0);
debug_assert!(first_entry <= self.providers.len());
if first_entry == self.providers.len() {
return ReturnValue::CONTINUE;
}
let url = CefString::from(&request.url()).to_string();
let url = (*self.url_filter)(&url);
let url = get_url_without_query_or_fragment(&url).to_string();
let state = RequestState {
manager: self.weak_self.clone(),
callback: Some(callback),
current_entry: first_entry,
current_request: 0,
params: Arc::new(RequestParams {
url,
browser,
frame,
request,
url_filter: self.url_filter.clone(),
mime_type_resolver: self.mime_type_resolver.clone(),
}),
};
ReturnValue::from(if self.send_request(state) {
sys::cef_return_value_t::RV_CONTINUE_ASYNC
} else {
sys::cef_return_value_t::RV_CONTINUE
})
}
pub fn resource_handler(
&mut self,
_browser: Browser,
_frame: Frame,
request: Request,
) -> Option<ResourceHandler> {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"resource_handler must be called on the IO thread"
);
self.pending_handlers.remove(&request.identifier())
}
fn send_request(&mut self, state: RequestState) -> bool {
let mut potentially_handled = false;
let current_entry = state.current_entry;
let params = state.params.clone();
let mut state = Some(state);
while state.is_some() {
debug_assert!(
current_entry < self.providers.len(),
"Should not be on the last provider entry."
);
let request = ResourceManagerRequest::new(state.take(), params.clone());
let Ok(mut request) = request.lock() else {
break;
};
state = request.send_request();
let Some(mut next_state) = state.take() else {
potentially_handled = true;
break;
};
if self.increment_provider(&mut next_state) {
state = Some(next_state);
} else {
self.stop_request(next_state);
}
}
potentially_handled
}
fn continue_request(&mut self, mut state: RequestState, handler: Option<ResourceHandler>) {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"continue_request must be called on the IO thread"
);
if let Some(handler) = handler {
self.pending_handlers
.insert(state.params.request.identifier(), handler);
self.stop_request(state);
} else {
if self.increment_provider(&mut state) {
self.send_request(state);
} else {
self.stop_request(state);
}
}
}
fn stop_request(&mut self, mut state: RequestState) {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"stop_request must be called on the IO thread"
);
self.detach_request_from_provider(&mut state);
}
fn increment_provider(&mut self, state: &mut RequestState) -> bool {
let next_entry = self.get_next_valid_provider(state.current_entry);
self.detach_request_from_provider(state);
if next_entry < self.providers.len() {
state.current_entry = next_entry;
true
} else {
false
}
}
fn detach_request_from_provider(&mut self, state: &mut RequestState) {
let Some(current_entry) = self.providers.get_mut(state.current_entry) else {
return;
};
current_entry.pending_requests.remove(state.current_request);
if current_entry.deletion_pending && current_entry.pending_requests.is_empty() {
self.providers.remove(state.current_entry);
}
state.current_entry = self.providers.len();
}
fn get_next_valid_provider(&self, current_provider: usize) -> usize {
self.providers
.iter()
.enumerate()
.skip(current_provider)
.find(|&(_, entry)| !entry.deletion_pending)
.map(|(index, _)| index)
.unwrap_or(self.providers.len())
}
fn delete_provider(&mut self, deleted_provider: usize, stop: bool) -> usize {
debug_assert_ne!(
currently_on(ThreadId::IO),
0,
"delete_provider must be called on the IO thread"
);
let Some(current_entry) = self.providers.get_mut(deleted_provider) else {
return self.providers.len();
};
if current_entry.deletion_pending {
return deleted_provider;
}
if !current_entry.pending_requests.is_empty() {
current_entry.deletion_pending = true;
for request in current_entry.pending_requests.iter() {
let Ok(mut request_lock) = request.lock() else {
continue;
};
if request_lock.state.is_some() {
if stop {
request_lock.stop_request();
} else {
request_lock.continue_request(None);
}
if let Some(provider) = ¤t_entry.provider {
provider.on_request_canceled(request.clone());
}
}
}
deleted_provider + 1
} else {
self.providers.remove(deleted_provider);
deleted_provider
}
}
}
pub fn get_url_without_query_or_fragment(url: &str) -> &str {
url.split(['?', '#']).next().unwrap_or(url)
}
pub fn get_mime_type(url: &str) -> String {
let url = get_url_without_query_or_fragment(url);
url.rsplit('.')
.next()
.map(|extension| {
let extension = CefString::from(extension);
CefString::from(&crate::get_mime_type(Some(&extension))).to_string()
})
.unwrap_or_else(|| String::from("text/html"))
}
pub fn get_filtered_url(url: &str) -> String {
url.to_string()
}
pub fn normalize_url_path(url_path: &str) -> String {
format!("{}/", url_path.trim_end_matches('/'))
}
pub fn get_file_relative_path(
mut base_path: PathBuf,
url_path: &str,
url: &str,
) -> Option<PathBuf> {
let segments = url.strip_prefix(url_path)?.split('/');
for segment in segments {
base_path.push(segment)
}
Some(base_path)
}