use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
use dom_struct::dom_struct;
use js::context::JSContext;
use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};
use script_bindings::root::DomRoot;
use servo_base::generic_channel::{GenericCallback, GenericSend};
use servo_url::ServoUrl;
use storage_traits::cache_storage::{CacheStorageThreadMessage, CacheStorageThreadResponse};
use crate::dom::Promise;
use crate::dom::bindings::codegen::Bindings::CacheBinding::CacheMethods;
use crate::dom::bindings::codegen::GenericBindings::CacheBinding::CacheQueryOptions;
use crate::dom::bindings::codegen::UnionTypes::RequestOrUSVString;
use crate::dom::bindings::error::Error;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::str::DOMString;
use crate::dom::fetch::request::Request;
use crate::dom::globalscope::GlobalScope;
#[dom_struct]
pub(crate) struct Cache {
reflector_: Reflector,
name: DOMString,
#[no_trace]
#[ignore_malloc_size_of = "GenericCallback"]
callback: RefCell<Option<GenericCallback<CacheStorageThreadResponse>>>,
#[conditional_malloc_size_of]
pending_promises: RefCell<VecDeque<Rc<Promise>>>,
}
impl Cache {
fn new_inherited(name: DOMString) -> Cache {
Cache {
reflector_: Reflector::new(),
name,
callback: Default::default(),
pending_promises: Default::default(),
}
}
pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope, name: DOMString) -> DomRoot<Cache> {
reflect_dom_object_with_cx(Box::new(Cache::new_inherited(name)), global, cx)
}
pub(crate) fn get_or_setup_callback(&self) -> GenericCallback<CacheStorageThreadResponse> {
if let Some(cb) = self.callback.borrow().as_ref() {
return cb.clone();
}
let global = self.global();
let response_listener = Trusted::new(self);
let task_source = global
.task_manager()
.dom_manipulation_task_source()
.to_sendable();
let callback = GenericCallback::new(move |message| {
let response_listener = response_listener.clone();
let response = match message {
Ok(inner) => Some(inner),
Err(err) => {
error!("Error in Cache callback {:?}.", err);
None
},
};
task_source.queue(task!(set_request_result_to_database: move |cx| {
let cache = response_listener.root();
cache.handle_response(cx, response)
}));
})
.expect("Could not create Cache callback");
*self.callback.borrow_mut() = Some(callback.clone());
callback
}
fn handle_response(&self, cx: &mut JSContext, response: Option<CacheStorageThreadResponse>) {
let response = match response {
Some(response) => response,
None => {
let Some(promise) = self.pending_promises.borrow_mut().pop_front() else {
error!("No pending promise for Cache response.");
return;
};
promise.reject_error(
cx,
Error::Operation(Some("No response from Cache backend.".to_string())),
);
return;
},
};
match response {
CacheStorageThreadResponse::KeysResult(results) => {
let mut request_list: Vec<DomRoot<Request>> = Vec::new();
let Some(promise) = self.pending_promises.borrow_mut().pop_front() else {
debug_assert!(false, "No pending promise for Cache KeysResult response.");
return;
};
for url in results.unwrap_or_default() {
let Ok(url) = ServoUrl::parse(&url) else {
promise.reject_error(cx, Error::Type(c"Invalid URL".to_owned()));
return;
};
let request = Request::new(cx, &self.global(), None, url);
request_list.push(request);
}
promise.resolve_native(cx, &request_list);
},
CacheStorageThreadResponse::DeleteCacheResult(_result) => debug_assert!(
false,
"Unexpected DeleteCacheResult response in Cache handle_response."
),
CacheStorageThreadResponse::HasCacheResult(_result) => debug_assert!(
false,
"Unexpected HasCacheResult response in Cache handle_response."
),
CacheStorageThreadResponse::OpenCacheResult { .. } => debug_assert!(
false,
"Unexpected OpenCacheResult response in Cache handle_response."
),
}
}
}
impl CacheMethods<crate::DomTypeHolder> for Cache {
fn Keys(
&self,
cx: &mut JSContext,
request: Option<RequestOrUSVString>,
_options: &CacheQueryOptions,
) -> Rc<Promise> {
let mut r: Option<DomRoot<Request>> = None;
let global = self.global();
let promise = Promise::new(cx, &global);
if let Some(request) = request {
if let RequestOrUSVString::Request(request) = request {
r = Some(request);
} else if let RequestOrUSVString::USVString(request_string) = request {
let Ok(url) = ServoUrl::parse(&request_string) else {
promise.reject_error(cx, Error::Type(c"Invalid URL".to_owned()));
return promise;
};
let request = Request::new(cx, &global, None, url);
r = Some(request);
}
}
let _ = r;
let callback = self.get_or_setup_callback();
if global
.storage_threads()
.send(CacheStorageThreadMessage::Keys {
cache_name: self.name.to_string(),
callback,
origin: global.origin().immutable().clone(),
})
.is_err()
{
promise.reject_error(
cx,
Error::Operation(Some("Could not run the parallel steps.".to_string())),
);
return promise;
}
self.pending_promises
.borrow_mut()
.push_back(promise.clone());
promise
}
}