use crate::util::IntoString;
use bevy::prelude::warn;
use cef::rc::{Rc, RcImpl};
use cef::{
CompletionCallback, ImplCompletionCallback, ImplCookieManager, ImplRequestContext,
RequestContext, WrapCompletionCallback, sys,
};
use std::cell::Cell;
use std::rc::Weak;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum FlushState {
Idle,
InFlight {
again: bool,
},
}
impl FlushState {
fn request(&mut self) -> bool {
match self {
Self::Idle => {
*self = Self::InFlight { again: false };
true
}
Self::InFlight { again } => {
*again = true;
false
}
}
}
fn complete(&mut self) -> bool {
match self {
Self::InFlight { again: true } => {
*self = Self::InFlight { again: false };
true
}
_ => {
*self = Self::Idle;
false
}
}
}
fn abandon(&mut self) -> bool {
let was_in_flight = matches!(self, Self::InFlight { .. });
*self = Self::Idle;
was_in_flight
}
}
struct FlushTarget {
context: RequestContext,
state: Cell<FlushState>,
}
impl FlushTarget {
fn request(self: &std::rc::Rc<Self>) {
let mut state = self.state.get();
let start = state.request();
self.state.set(state);
if start {
self.start();
}
}
fn complete(self: &std::rc::Rc<Self>) {
let mut state = self.state.get();
let again = state.complete();
self.state.set(state);
if again {
self.start();
}
}
fn start(self: &std::rc::Rc<Self>) {
let Some(manager) = self.context.cookie_manager(None) else {
warn!(
"bevy_cef: profile {} has no cookie manager; cookies were not flushed",
self.cache_path()
);
self.state.set(FlushState::Idle);
return;
};
let mut callback = FlushCompletionBuilder::build(std::rc::Rc::downgrade(self));
if manager.flush_store(Some(&mut callback)) == 0 {
warn!(
"bevy_cef: CEF refused to flush the cookie store of profile {}",
self.cache_path()
);
self.state.set(FlushState::Idle);
}
}
fn cache_path(&self) -> String {
self.context.cache_path().into_string()
}
}
pub(crate) struct ProfileFlusher {
target: std::rc::Rc<FlushTarget>,
}
impl ProfileFlusher {
pub fn new(context: RequestContext) -> Self {
Self {
target: std::rc::Rc::new(FlushTarget {
context,
state: Cell::new(FlushState::Idle),
}),
}
}
pub fn request(&self) {
self.target.request();
}
pub fn handle(&self) -> CookieFlusher {
CookieFlusher {
target: std::rc::Rc::downgrade(&self.target),
}
}
}
#[derive(Clone)]
pub struct CookieFlusher {
target: Weak<FlushTarget>,
}
impl CookieFlusher {
pub fn request(&self) {
if let Some(target) = self.target.upgrade() {
target.request();
}
}
}
struct CompletionGuard {
target: Weak<FlushTarget>,
completed: Cell<bool>,
}
impl Drop for CompletionGuard {
fn drop(&mut self) {
if self.completed.get() {
return;
}
let Some(target) = self.target.upgrade() else {
return;
};
let mut state = target.state.get();
if state.abandon() {
target.state.set(state);
warn!(
"bevy_cef: CEF dropped the cookie flush of profile {} without completing it",
target.cache_path()
);
}
}
}
struct FlushCompletionBuilder {
object: *mut RcImpl<sys::_cef_completion_callback_t, Self>,
guard: std::rc::Rc<CompletionGuard>,
}
impl FlushCompletionBuilder {
fn build(target: Weak<FlushTarget>) -> CompletionCallback {
CompletionCallback::new(Self {
object: core::ptr::null_mut(),
guard: std::rc::Rc::new(CompletionGuard {
target,
completed: Cell::new(false),
}),
})
}
}
impl Rc for FlushCompletionBuilder {
fn as_base(&self) -> &sys::cef_base_ref_counted_t {
unsafe {
let base = &*self.object;
core::mem::transmute(&base.cef_object)
}
}
}
impl Clone for FlushCompletionBuilder {
fn clone(&self) -> Self {
let object = unsafe {
let rc_impl = &mut *self.object;
rc_impl.interface.add_ref();
rc_impl
};
Self {
object,
guard: self.guard.clone(),
}
}
}
impl WrapCompletionCallback for FlushCompletionBuilder {
fn wrap_rc(&mut self, object: *mut RcImpl<sys::_cef_completion_callback_t, Self>) {
self.object = object;
}
}
impl ImplCompletionCallback for FlushCompletionBuilder {
fn on_complete(&self) {
self.guard.completed.set(true);
if let Some(target) = self.guard.target.upgrade() {
target.complete();
}
}
#[inline]
fn get_raw(&self) -> *mut sys::_cef_completion_callback_t {
self.object.cast()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_idle_request_starts_a_flush() {
let mut state = FlushState::Idle;
assert!(state.request());
assert_eq!(state, FlushState::InFlight { again: false });
}
#[test]
fn requests_during_a_flush_coalesce_into_one_follow_up() {
let mut state = FlushState::InFlight { again: false };
assert!(!state.request());
assert!(!state.request());
assert_eq!(state, FlushState::InFlight { again: true });
assert!(state.complete());
assert!(!state.complete());
assert_eq!(state, FlushState::Idle);
}
#[test]
fn an_abandoned_flush_returns_to_idle() {
let mut state = FlushState::InFlight { again: true };
assert!(state.abandon());
assert_eq!(state, FlushState::Idle);
assert!(!state.abandon());
assert!(state.request());
}
}