use std::collections::HashMap;
use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
use cef::*;
pub use buffr_engine::{OsrFrame, OsrViewState, SharedOsrFrame, SharedOsrViewState};
pub type PopupFrameMap = Arc<Mutex<HashMap<i32, (SharedOsrFrame, SharedOsrViewState)>>>;
wrap_render_handler! {
pub struct OsrPaintHandler {
main_id: Arc<AtomicI32>,
frame: SharedOsrFrame,
view: SharedOsrViewState,
popup_frames: PopupFrameMap,
loading_busy: Arc<AtomicBool>,
}
impl RenderHandler {
fn view_rect(&self, browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
let Some(rect) = rect else { return };
let (w, h) = self.resolve_dims(browser.as_deref().map(|b| b.identifier()));
rect.x = 0;
rect.y = 0;
rect.width = w as i32;
rect.height = h as i32;
tracing::debug!(browser_id = ?browser.as_deref().map(|b| b.identifier()), w = rect.width, h = rect.height, "osr: view_rect queried");
}
fn screen_info(
&self,
browser: Option<&mut Browser>,
screen_info: Option<&mut ScreenInfo>,
) -> ::std::os::raw::c_int {
let Some(si) = screen_info else {
tracing::trace!("osr: screen_info — screen_info arg is None");
return 0;
};
let browser_id = browser.as_deref().map(|b| b.identifier());
let (w, h) = self.resolve_dims(browser_id);
let scale = self.resolve_scale(browser_id);
tracing::debug!(
?browser_id,
w,
h,
scale,
"osr: screen_info queried",
);
si.device_scale_factor = scale;
si.depth = 32;
si.depth_per_component = 8;
si.is_monochrome = 0;
si.rect = Rect {
x: 0,
y: 0,
width: w as i32,
height: h as i32,
};
si.available_rect = si.rect.clone();
1
}
fn screen_point(
&self,
_browser: Option<&mut Browser>,
view_x: ::std::os::raw::c_int,
view_y: ::std::os::raw::c_int,
screen_x: Option<&mut ::std::os::raw::c_int>,
screen_y: Option<&mut ::std::os::raw::c_int>,
) -> ::std::os::raw::c_int {
if let Some(sx) = screen_x {
*sx = view_x;
}
if let Some(sy) = screen_y {
*sy = view_y;
}
1
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn on_paint(
&self,
browser: Option<&mut Browser>,
type_: PaintElementType,
_dirty_rects: Option<&[Rect]>,
buffer: *const u8,
width: ::std::os::raw::c_int,
height: ::std::os::raw::c_int,
) {
if type_.get_raw() != PaintElementType::VIEW.get_raw() {
tracing::trace!("osr: on_paint Popup — deferred (TODO: composite popup)");
return;
}
let browser_id = browser.as_deref().map(|b| b.identifier());
let w = width as u32;
let h = height as u32;
let len = (w as usize) * (h as usize) * 4;
tracing::trace!(w, h, ?browser_id, "osr: on_paint fired");
let src = unsafe { std::slice::from_raw_parts(buffer, len) };
let (frame, view) = match self.resolve_frame_view(browser_id) {
Some(pair) => pair,
None => {
tracing::trace!(
?browser_id,
"osr: on_paint — unknown browser id, skipping"
);
return;
}
};
let Ok(mut guard) = frame.lock() else {
tracing::warn!("osr: on_paint — frame mutex poisoned, skipping");
return;
};
if guard.width != w || guard.height != h || guard.pixels.len() != len {
if guard.width != w || guard.height != h {
tracing::debug!(
old_w = guard.width,
old_h = guard.height,
new_w = w,
new_h = h,
"osr: on_paint dimension change",
);
}
guard.pixels.resize(len, 0);
guard.width = w;
guard.height = h;
}
guard.pixels.copy_from_slice(src);
guard.generation = guard.generation.wrapping_add(1);
guard.needs_fresh = false;
drop(guard);
self.loading_busy.store(false, Ordering::Relaxed);
if let Some(wake) = view.wake.get() {
wake();
}
}
}
}
impl OsrPaintHandler {
fn resolve_scale(&self, browser_id: Option<i32>) -> f32 {
if let Some(id) = browser_id {
let main = self.main_id.load(Ordering::Relaxed);
if main == id || main == -1 {
return self.view.scale();
}
if let Ok(map) = self.popup_frames.lock()
&& let Some((_, popup_view)) = map.get(&id)
{
return popup_view.scale();
}
}
self.view.scale()
}
fn resolve_dims(&self, browser_id: Option<i32>) -> (u32, u32) {
if let Some(id) = browser_id {
let main = self.main_id.load(Ordering::Relaxed);
if main == id || main == -1 {
if main == -1 {
self.main_id.store(id, Ordering::Relaxed);
}
return (
self.view.width.load(Ordering::Relaxed),
self.view.height.load(Ordering::Relaxed),
);
}
if let Ok(map) = self.popup_frames.lock()
&& let Some((_, popup_view)) = map.get(&id)
{
return (
popup_view.width.load(Ordering::Relaxed),
popup_view.height.load(Ordering::Relaxed),
);
}
}
(
self.view.width.load(Ordering::Relaxed),
self.view.height.load(Ordering::Relaxed),
)
}
fn resolve_frame_view(
&self,
browser_id: Option<i32>,
) -> Option<(SharedOsrFrame, SharedOsrViewState)> {
let id = browser_id?;
let main = self.main_id.load(Ordering::Relaxed);
if main == -1 || main == id {
if main == -1 {
self.main_id.store(id, Ordering::Relaxed);
}
return Some((self.frame.clone(), self.view.clone()));
}
if let Ok(map) = self.popup_frames.lock()
&& let Some((pf, pv)) = map.get(&id)
{
return Some((pf.clone(), pv.clone()));
}
None
}
}
pub fn make_osr_paint_handler(
frame: SharedOsrFrame,
view: SharedOsrViewState,
popup_frames: PopupFrameMap,
loading_busy: Arc<AtomicBool>,
) -> RenderHandler {
OsrPaintHandler::new(
Arc::new(AtomicI32::new(-1)),
frame,
view,
popup_frames,
loading_busy,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_view_dims_and_scale() {
let v = OsrViewState::new();
assert_eq!(v.width.load(Ordering::Relaxed), 1280);
assert_eq!(v.height.load(Ordering::Relaxed), 800);
assert!((v.scale() - 1.0).abs() < 1e-6);
}
#[test]
fn set_scale_does_not_touch_dims() {
let v = OsrViewState::new();
v.width.store(1500, Ordering::Relaxed);
v.height.store(1050, Ordering::Relaxed);
v.set_scale(2.0);
assert_eq!(v.width.load(Ordering::Relaxed), 1500);
assert_eq!(v.height.load(Ordering::Relaxed), 1050);
assert!((v.scale() - 2.0).abs() < 1e-6);
}
#[test]
fn set_scale_round_trips_thousandths() {
let v = OsrViewState::new();
v.set_scale(1.25);
assert!((v.scale() - 1.25).abs() < 1e-3);
v.set_scale(1.5);
assert!((v.scale() - 1.5).abs() < 1e-3);
v.set_scale(2.0);
assert!((v.scale() - 2.0).abs() < 1e-3);
}
#[test]
fn set_scale_clamps_to_at_least_one_thousandth() {
let v = OsrViewState::new();
v.set_scale(0.0);
assert!(v.scale() > 0.0);
}
#[test]
fn dim_writes_independent_of_scale() {
let v = OsrViewState::new();
v.set_scale(1.5);
v.width.store(2000, Ordering::Relaxed);
v.height.store(1400, Ordering::Relaxed);
assert!((v.scale() - 1.5).abs() < 1e-3);
assert_eq!(v.width.load(Ordering::Relaxed), 2000);
assert_eq!(v.height.load(Ordering::Relaxed), 1400);
}
}